MemMap: Map of DOS Memory Blocks Using Turbo Pascal 5.0 by Earl F. Glynn Overland Park, KS CompuServe 73257,3527 (C) Copyright 1989, All Rights Reserved. Introduction ------------ MemMap is a DOS utility that displays all allocated memory blocks, including terminate-and-stay-resident (TSR) programs and their associated environment memory blocks. With a /V switch, MemMap displays the variables in each environment block. MemMap has been tested with DOS 2.10, 3.3, 4.0 and OS/2 Extended Edition 1.1 DOS Command subset. The original motivation for writing MemMap was to determine the amount of memory lost to needless environment variables being saved every time a TSR was loaded into memory. But several other factors shaped the development of MemMap: - DOS 4.0 "MEM /DEBUG" functionality is not present in DOS 3.X. Some utilities, such as PC Magazine's PCMAP, give part of the information desired about TSRs. - PCMAP returns "unknown" for most program names when run under DOS 4.0 (See Figure 1). Since MEM is a new command under DOS 4.0, and cannot be run on earlier DOS versions, a common program that operates under both DOS 3.X and 4.X is desirable. - DOS 4.0 MEM reports block sizes only in hexadecimal bytes, e.g., Sidekick (SKM) is hex 7180 bytes long, instead of decimal 29056 bytes. - PCMAP aggregates both environment and program blocks, creating two problems: (1) the memory entries are not always listed in ascending order; (2) size information about individual environment blocks is not available. - The size and contents of the environment blocks cannot be effectively managed without an easy way to display all variables in all environment blocks. - Several memory map programs have erratic behavior when IBM's Workstation Program (INDCIPL.EXE) and Expanded Memory Manager (INDXMAA.SYS) are in memory. - Turbo Pascal can be used to write system utilities as easily as assembly language or C. In particular, MemMap demonstrates the ability of Turbo Pascal to manipulate DOS system control blocks using pointer variables and record types. Background ---------- The layout of DOS user memory is shown in Figure 2. Following the DOS system area, each memory block is preceded by a memory control block (MCB). This MCB stores information on the owner of the block and the size of the memory block -- described in Figure 3. Once the initial MCB is found, the next MCB follows the memory block. All of user memory can be mapped by stepping through, MCB by MCB. The last MCB is tagged differently than all other MCBs, so a termination condition is easily recognized. But how is the address of the first MCB found? The first Memory Control Block can be found by searching through memory at paragraph boundaries, or by using the undocumented function call $52 of DOS interrupt $21. MemMap uses the latter approach to obtain the address of the so-called DOS "List of Lists". At an offset of -2 in this "List of Lists" is the segment address of the first MCB. The "Type" of an MCB is determined by observing when certain relationships exist. If the MCB owner is hex 0000, the block following the MCB is not allocated and its type is shown as "Free Space". When the first word of the paragraph following the MCB is hex 20CD (or possibly 27CD), the block type is "Program", if the "owner" field points to this next paragraph; otherwise, the "owner" is assumed to be the DOS kernel and the block type is shown as "System". See Figure 4. The block type is "Environment" when the block's owner has an environment pointer back to the paragraph following the MCB. When none of the above relationships hold, the block is assumed to have a "Data" type. (One TSR that I've found with a "Data" memory block is PC Magazine's PRN2FILE). The "Name" of an MCB is determined from the environment block when possible. In DOS 3.X, the fully-qualified program name follows the environment value(s) and a special tag word (hex 0001), as shown in Figure 4. When an environment block does not exist for a program, or when the program name does not follow the environment block, a "short" program name (1-8 characters) can be found in the last half of the MCB -- but only in DOS 4.0. In earlier DOS versions the name is "" in such a case. Implementation Details for Turbo Pascal 5.0 ------------------------------------------- Pointers are usually used to address dynamic variables created with the standard Pascal NEW procedure (or Turbo Pascal's GetMem procedure), but pointers can also be used to manipulate existing data structures, such as DOS memory blocks. An example of accessing DOS's "List of Lists", Memory Control Blocks (MCBs) and the Program Segment Prefix (PSP) blocks using pointer variables is shown in the MemMap Turbo Pascal 5.0 program. (See Figure 5). To obtain the address of the DOS "List of Lists", the undocumented function call $52 of DOS interrupt $21 is needed. The DOS Turbo Pascal UNIT provides the "Intr" procedure and "Registers" type: ... VAR r: Registers; {Figure 5, line 57} ... r.AH := $52; {line 205} Intr ($21,r); {line 206} After this DOS interrupt, a pointer to the "List of Lists" is in ES:BX. At an offset of -2 in the "List of Lists" is the segment address of the first MCB: segment := MemW[r.ES:r.BX-2]; {line 207} where the "MemW" Turbo Pascal "system array" returns the word at the given segment:offset address. The "MCB" pointer variable is then defined using the Turbo Pascal "Ptr" procedure: MCB := Ptr(segment,0); {line 210} At this point all the variables defined by the MemoryControlBlock TYPE can be accessed. Typecasting can be used to help calculate the size of the memory block following the MCB: bytes := LongInt(MCB^.BlockSize) SHL 4; {line 102} If the MCB^.BlockOwner is zero, the block is free space. If it is nonzero, the MCB^.BlockOwner points to a memory block, which is a valid Program Segment Prefix (PSP) if it starts with the word $20CD (or possibly $27CD). The PSP pointer is defined by psp := Ptr(MCB^.BlockOwner,0); {line 109} A segment pointer to the environment block is at an offset of hex 2C from the beginning of the PSP, which is defined by psp^.Environment. Since the environment block must be immediately preceded by an MCB, the environment MCB is defined by: MCBenv := Ptr(psp^.Environment-1,0); {line 116} If the MCB^.BlockOwner is the same as the MCBenv^.BlockOwner, environment variables follow in ASCIIZ delimited strings (character strings ending with a $00). A null (zero-length) variable marks the end of the environment. The program name follows the special $0001 tag. Once an MCB and its associated information is displayed, the segment address of the next MCB is found by the statement: segment := segment + MCB^.Blocksize + 1; {line 212} The extra "1" above is needed since the MCB is itself a single paragraph long. Contiguous MCBs and memory blocks can be sequentially listed until the last MCB is found, which is tagged with MCB^.Blocktag = $5A. A final note: DO NOT use DISPOSE (or FreeMem) to deallocate a pointer variable that was not allocated using NEW (or GetMem). Since the DOS control blocks described above are allocated outside of MemMap, they should not be deallocated by MemMap. Results ------- To demonstrate MemMap, several DOS commands and other TSRs were executed to create a variety of memory control blocks (after booting without an AUTOEXEC.BAT or CONFIG.SYS present): APPEND .. GRAPHICS MODE LPT1=COM1 \TSR\DOSED \PCMAG\INSTALL \PCMAG\CAPTURE \PCMAG\CONFIRM \PCMAG\PRN2FILE \PCMAG\PUSHDIR \PCMAG\POP-CAL \SIDEKICK\SKM Figure 6 shows MemMap's output from PC DOS 3.3. Note that the name of several programs cannot be determined, namely the MCB at 103E (CAPTURE), 1190 and 1200 (PRN2FILE) and 1301 (PUSHDIR). With the same set of TSRs loaded with DOS 4.0, Figure 7 shows the "short" 1-8 character names of all programs "unknown" to DOS 3.3, since these names were stored in the block owner's MCB. For comparison, the output from the DOS 4.0 "MEM /DEBUG" command is shown in Figure 8. The contents of the environment blocks can be determined using the /V switch of MemMap. Figure 9 shows the output of "MEMMAP /V". Note in Figure 9 that OS/2 EE 1.1 DOS subset reports itself as DOS 10.10. But the MemMap listing for OS/2 DOS subset is more like DOS 3.3 than DOS 4.0. The current environment variables maintained by DOS are in the first environment block with a name of "". The size of this first block is 160 bytes in Figures 6 and 7 since this is the default environment size. The size of this first environment block can be increased by adding a statement to the CONFIG.SYS (and re-booting). For example, to increase the environment space to 512 bytes, add the following to CONFIG.SYS: SHELL=\COMMAND.COM /P /E:512 In addition to the working set of environment variables set up at boot time, DOS makes a copy of the variables in effect at the time a TSR is loaded. The size of each of these environment blocks is adjusted to accommodate only the variables set at the time the TSR is loaded. This is why DOS 3.3 shows 10 environment blocks (Figure 6) and DOS 4.0 shows 7 environment blocks (Figure 7). Apparently, the DOS 4.0 commands APPEND, GRAPHICS and MODE are now "smart" enough not to make copies of the environment variables. Inspection of output using "MEMMAP /V" results in a few "rules" to minimize unnecessary, wasted memory: 1. Load TSRs with the smallest possible set of environment variables in effect. If possible, reduce the set to COMSPEC and a very short PATH. 2. In AUTOEXEC.BATs, load TSRs before PATH or SETs, if possible. Use fully-qualified names to load TSRs so the PATH can be reduced. 3. Never re-execute AUTOEXEC.BAT after bootup, if it contains TSRs that re-install themselves multiple times. Many TSRs do not check to see if they are already resident. MemMap can quickly determine when a TSR is loaded into memory multiple times. Even when these "rules" are followed in loading TSRs, Figure 6 shows 576 bytes lost to environment blocks -- not counting the 160-byte boot-time "current" environment block. Hundreds of bytes can be lost to needless copies of environment variables if care is not taken. A Memory Enigma --------------- The IBM 3270 Workstation Program (INDCIPL.EXE) allows mainframe host "sessions" and PC "sessions" with a hotkey between them. Depending on how INDCIPL is configured, DOS conventional memory can be very limited, even when the IBM Expanded Memory Manager (INDXMAA.SYS) is used. The state of DOS conventional memory from a PC session under INDCIPL is shown in Figure 10 from both PCMAP and MemMap. Note both programs report a large number of null MCBs -- MCBs pointing to zero-length memory blocks. The reason the two programs are not consistent, and the reason why only a portion of conventional memory appears to be available has not yet been explained. This should not be surprising since much "undocumented" information is used by PCMAP and MemMap. Information about MCBs is not in the IBM DOS 3.3 Technical Reference Manual, but rather books such as "The Waite's Group MS-DOS Developer's Guide". These null MCBs appear to be a trick to reserve large memory chunks and make them unavailable to "normal" DOS applications. In the two MemMap cases shown in Figure 10, a 51K chunk and a 312K chunk of memory are taken up with consecutive null MCBs that appear to be placeholders. Summary ------- MemMap demonstrates Turbo Pascal can easily manipulate DOS control blocks using pointer variables and record types. MemMap has 215 Pascal lines and creates a 6128-byte EXE file. By contrast, PCMAP has 300 assembly language statements and creates a 643-byte COM file (stored in a 2048-byte cluster on most hard disks). While certain efficiencies are lost in a high-level language, like Pascal, when writing system utilities, often the maintainability is much easier in the high level language. Besides, it's not difficult to improve on certain DOS components, such as the PC DOS 4.0 MEM command: MEM is a 20133-byte EXE file -- more than 3 times larger than the MEMMAP.EXE program.